﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using TrainGame.Model;
using TrainGame.Common;

namespace TrainGame.AI.TileLay
{
	static public class AiTilePicker
	{
		static private AiGoal[] s_tileLayGoals;
		static private List<string> s_citiesToHeadToward;

		/// <summary>
		/// static CTOR so we can control static-member creation order
		/// </summary>
		static AiTilePicker()
		{
			s_citiesToHeadToward = new List<string>(new string[]
			{
				"F15",	// London
				"M04",	// Barrie
				"N11",	// Toronto
				"L15"	// Hamilton
			});
			s_tileLayGoals = new AiGoal[]
			{
				new ImproveCurrentRoutesTileGoal(),
				new ImproveRoutesForPlannedTrainsTileGoal(),
				new HeadForDestinationTileGoal(),
				new HeadForCityTileGoal(s_citiesToHeadToward),
				new HelpOtherCompanyTileGoal(),
				//new HeadForTokenSpotTileGoal(),
				//new OldTileLayAiTileGoal (),
			};
		}

		/// <summary>
		/// The main entry point to choose a tile lay.
		/// </summary>
		/// <param name="game"></param>
		/// <param name="config"></param>
		/// <param name="origGameboard"></param>
		/// <param name="tiles"></param>
		/// <param name="company"></param>
		/// <param name="aiGoal"></param>
		/// <returns></returns>
		static public AiTileLay ChooseTileLay(AiMoveConfig config, Gameboard origGameboard, Tiles tiles, PublicCompany company,
			out string aiGoal /*, AiPersonality personality*/)
		{
			int seconds = Debugger.IsAttached ? 9999 : 5;	// set a 5-second limit on computations (or 9999 for debugging)
			TimeSpan timeLimit = TimeSpan.FromSeconds(seconds);
			Stopwatch watch = new Stopwatch();
			watch.Start();

			// make copy of game board tiles and hexes, so we can modify it without affecting the real game board (GetTileLaysForHexes touches the gameboard)
			Gameboard gameboard = origGameboard.MakeCopy();
			origGameboard = null;	// lose the reference to real gameboard so we don't accidentally modify it

			Dictionary<string, GameboardHex> runToHexes;
			Dictionary<string, GameboardHex> tileLayHexes;
			GetReachableHexes(gameboard, company, out runToHexes, out tileLayHexes);

			List<AiTileLay> tileLays = GetTileLaysForHexes(config.Game, config, gameboard, tiles, tileLayHexes, company);
			List<AiMove> bestTileLays = null;

			// TODO: might be better to loop on each tile lay and test it through each goal, add each goal's value for each tile lay
			// TODO: each goal may have to precompute stuff and use it in each loop

			// For now we'll just pick the highest-need goal.
			// Eventually we may want to multiply need x delivered value, or even see if the same tile lay serves multiple goals
			List<AiGoal> Goals = new List<AiGoal>();
//			Dictionary<AiGoal, int> Goals = new Dictionary<AiGoal, int>();
			AiTileLay tileLay = null;
			foreach (AiGoal goal in s_tileLayGoals)
			{
				goal.Need = goal.ComputeNeedValue(gameboard, company, runToHexes, config);
				Goals.Add(goal);
				XTrace.Trace("   ** AI: goal {0} need = {1}", goal.GetName(company), goal.Need);
			}
			// Since goals with need might fail to find a meaningful tile lay, we need to walk down the list until a goal returns a tile lay
			AiGoal tileGoal = null;
			AiGoal.SortHighestToLowest (Goals);
			for (int i=0; i <= Goals.Count; i++ )
			{
				AiGoal goal = Goals[i];
				bestTileLays = goal.ComputeMove(watch, timeLimit, gameboard, company, config, tileLays, runToHexes, tileLayHexes);
				if ((bestTileLays != null) && (bestTileLays.Count > 0))
				{
					tileGoal = goal;
					break;
				}
				else
					XTrace.Trace("    * {0}: No AI tile lays found ***", goal.GetName(company));
			}
			if (tileGoal != null)
			{
				aiGoal = tileGoal.GetName(company);
				XTrace.Trace("  *** AI chose goal {0}", aiGoal);
			}
			else
			{
				aiGoal = null;
				XTrace.Trace("  *** AI did not find a tile lay goal!  No tile lay will be made.");
			}

			watch.Stop();
			XTrace.Trace("  *** AiTileLay ChooseTileLay duration " + watch.Elapsed.ToString());

			//
			//  See how many tile lays were returned, and pick one randomly if more than one.
			if ((bestTileLays == null) || (bestTileLays.Count == 0))
			{
				XTrace.Trace("  *** {0}: No AI tile lays found ***", aiGoal);
				return null;
			}
			if (bestTileLays.Count == 1)
			{
				tileLay = (AiTileLay) bestTileLays[0];
				XTrace.Trace("  *** {0}: 1 AI tile lay found (value={1}) ***", aiGoal, tileLay.GoalValue);
				return tileLay;
			}

			// if there are multiple best tile lays, pick a random one
			Random rand = new Random();
			int index = rand.Next(bestTileLays.Count);

			// trace it
			tileLay = (AiTileLay) bestTileLays[index];
			XTrace.Trace("  *** {0} {1} AI tile lays found (value={2}) ***", aiGoal, bestTileLays.Count, tileLay.GoalValue);
			for (int i = 0; i < bestTileLays.Count; i++)
			{
				AiTileLay tl = (AiTileLay) bestTileLays[i];
				XTrace.Trace("    {0} {1}: tile {2} at rotation {3} in hex {4}, {5}",
					(i == index) ? "=>" : "  ",
					i,
					tl.Tile.Name,
					tl.Rotation,
					tl.Hex.Location,
					tl.DescribeTileLay());
			}
			return tileLay;
		}

		static private void GetReachableHexes(Gameboard gameboard, PublicCompany company,
			out Dictionary<string, GameboardHex> runToHexes, out Dictionary<string, GameboardHex> tileLayHexes)
		{
			company.CreateHexVisitations ();

			runToHexes = new Dictionary<string, GameboardHex>();
			tileLayHexes = new Dictionary<string, GameboardHex>();

			foreach (GameboardHex hex in gameboard.GameboardHexes)
			{
				// hexes we can reach with a tile lay
				if (company.HexVisits.ContainsKey(hex.Location))
					tileLayHexes.Add(hex.Location, hex);

				// hexes this company already travels to (and track already exists in)
				if ( ((hex.Tile != null) || (hex.HexType == GameboardHex.eHexType.OffBoard) || (! String.IsNullOrEmpty (hex.InitialTile))) &&
				  hex.CanCompanyReachAndHasRouteToToken(company) )
					runToHexes.Add(hex.Location, hex);
			}
		}

		static private List<AiTileLay> GetTileLaysForHexes(Game game, AiMoveConfig config, Gameboard gameboard, Tiles tiles, Dictionary<string, GameboardHex> hexes, PublicCompany company)
		{
			List<AiTileLay> tileLays = new List<AiTileLay>();
			string sError=null;
			foreach (GameboardHex hex in hexes.Values)
			{
				Tile origTile = hex.Tile;
				if (hex.CanLayTile(game, ref sError))
				{
					foreach (Tile tile in tiles)
					{
						if ((tile.QuantityLeft > 0) && (config.AllowTile(tile)))
						{
							bool CanLay = false;

							// TODO: store oldTile and newTile in tilelay so we can eventually
							//							oldTile.QuantityLeft++;
							//							newTile.QuantityLeft--;
							if (hex.Tile == null)
							{
								if (hex.IsValidTile(game, tile, ref sError) && (hex.Cost <= company.Cash))
								{
									CanLay = true;
									hex.Tile = tile;
								}
							}
							else if (hex.CanUpgradeTo(game, tile, ref sError) || hex.CanDowngradeTo(game, tile, ref sError))
							{
								CanLay = true;
								hex.Tile = tile;
							}

							// tile can go here, find all possible rotations for it
							if (CanLay)
							{
								int startRotation = hex.Rotation;
								do
								{
									if (hex.IsValidRotation(game, company))
										tileLays.Add(new AiTileLay(tile, hex, hex.Rotation));
									hex.Rotate();
								}
								while (hex.Rotation != startRotation);
							}
						}
						hex.Tile = origTile;
					}
				}
			}
			return tileLays;
		}
	}
}
